// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <algorithm>
#include <memory>
#include <string>

#include <fmt/core.h>

#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/base/symbology/product_info.h"
#include "coin2/exchange/factory/symbology/product.h"
#include "coin2/strategy/order_executor.pb.h"
#include "coin2/strategy/order_executor/cancel_executor.h"
#include "coin2/strategy/order_executor/working_order_state.h"
#include "coin2/strategy/util/moving_window.h"
#include "coin2/strategy/util/top_book.h"

namespace coin2::strategy::order_executor {

bool InBp(double x, double y, double bp);
bool InPrice(double x, double y, double delta);

class PassiveOrderExecutor : public exchange::base::order::IOrderSubscriber {
 public:
  using IOrderSubsystem = exchange::base::order::IOrderSubsystem;
  using OrderQuerier = exchange::base::order::OrderQuerier;
  using IProduct = coin2::exchange::base::symbology::IProduct;
  using IProductHolder = coin2::exchange::base::symbology::IProductHolder;
  using IProductInfo = coin2::exchange::base::symbology::IProductInfo;
  using OrderDirection = coin::proto::OrderDirection;
  using OrderDuration = coin::proto::OrderDuration;
  using OrderExecutorSystemConfig = coin2::strategy::AggressiveExecutorConfig;
  using OrderSide = coin::proto::OrderSide;
  using OrderSpec = coin2::exchange::base::order::OrderSpec;
  using OrderType = coin::proto::OrderType;
  using TradeSide = coin2::exchange::feed::TradeSide;
  using OrderUpdate = coin2::exchange::base::order::OrderUpdate;
  using TopBook = coin2::strategy::util::TopBook;
  using FillSource = coin::proto::FillSource;
  using TickPrice = coin2::exchange::base::symbology::TickPrice;
  using TickQty = coin2::exchange::base::symbology::TickQty;
  using FeedUpdate = coin2::exchange::base::feed::FeedUpdate;
  using OrderContext = coin2::exchange::base::order::OrderContext;
  using GetProductHolderFunc =
      std::function<const IProductHolder*(const IProduct& product, int64_t timestamp)>;

 public:
  /*
   PassiveOrderExecutor(
       const PassiveExecutorConfig& config,
       IOrderSubsystem* os,
       std::optional<int64_t> timestamp,
       const TopBook* book = nullptr);
       */

  PassiveOrderExecutor(
      const PassiveExecutorConfig& config,
      IOrderSubsystem* os,
      std::optional<int64_t> timestamp,
      const TopBook* book,
      GetProductHolderFunc get_product_holder);

  void ManageOrders(int64_t timestamp);
  void ManageMmOrders(
      int64_t timestamp,
      double sell_price,
      double buy_price,
      double sell_qty,
      double buy_qty,
      bool cancel_all_existing_sell,
      bool cancel_all_existing_buy);

  void ForceFireSellOrder(
      int64_t timestamp,
      double sell_price,
      double sell_qty,
      const std::string& tag = "");
  void ForceFireBuyOrder(
      int64_t timestamp,
      double buy_price,
      double buy_qty,
      const std::string& tag = "");
  const PassiveExecutorConfig& config() const { return config_; }
  void UpdateReduceOnly(double reduce_only);
  void UpdateLotSize(double lot_size);
  void UpdateMinMaxPosition(
      std::optional<double> min_position,
      std::optional<double> max_position,
      int64_t timestamp);

  void onTradeFeed(const FeedUpdate& upd);
  void onBookFeed(const FeedUpdate& upd);
  void onAccountInfo(const OrderUpdate& upd) override;
  void onAccountOrder(const OrderUpdate& upd) override;
  void onOrderLog(const coin::proto::OrderGatewayLog&) override{};  // legacy interface

  double GetPosition();
  double GetLongPosition() { return oq_->open_long_avail_qty(product()); }
  double GetTotalLongPosition() { return oq_->open_long_qty(product()); }
  double GetShortPosition() { return -oq_->open_short_avail_qty(product()); }
  double GetTotalShortPosition() { return -oq_->open_short_qty(product()); }

  double GetWorkingQty() { return wo_->GetBuyPotential() + wo_->GetSellPotential(); }

  const IProduct& product() { return product_holder_->product(); }
  const IProductInfo& product_info() { return product_holder_->product_info(); }
  const bool& GetEnableOrderSubmission() const { return enable_order_submission_; }

  void SetReduceOnly(bool reduce_only) { config_.set_reduce_only(reduce_only); }

  void CancelOrders();
  int ForceCancelAllWorkingOrders();
  int TryCancelAllWorkingOrders(int64_t timestamp);
  int TryCancelAllWorkingOrdersExceptTag(int64_t timestamp, std::string tag);
  void LogStatus() const;
  void SetMmTag(const std::string& tag) { tag_ = tag; }
  int SubmitBuyOrder(int64_t timestamp, double buy_price, double buy_qty);
  int SubmitSellOrder(int64_t timestamp, double buy_price, double buy_qty);
  int RiskCheckBuy(double qty);
  int RiskCheckSell(double qty);
  void SetBuyTimestamp(int64_t timestamp) { last_buy_ts_ = timestamp; }
  void SetSellTimestamp(int64_t timestamp) { last_sell_ts_ = timestamp; }
  void UpdateState(int64_t timestamp);
  int64_t LastBuyTimestamp() const { return last_buy_ts_; }
  int64_t LastSellTimestamp() const { return last_sell_ts_; }
  coin2::strategy::util::MovingSum<double>* BuyFills() const { return buy_fills_.get(); }
  coin2::strategy::util::MovingSum<double>* SellFills() const { return sell_fills_.get(); }
  double FillCoolDownQty() const { return fill_cooldown_qty_; }
  CancelExecutor* GetCancelExecutor() { return cancel_executor_.get(); }
  void SetEnableOrderSubmission(bool flag) { enable_order_submission_ = flag; }

 private:
  void AdjustQty(double* sell_qty, double* buy_qty);
  void TryFireSellOrder(int64_t timestamp, double sell_price, double sell_qty);
  void TryFireBuyOrder(int64_t timestamp, double buy_price, double buy_qty);
  void ManageRisk(int64_t timestamp);

  void AdjustRequest(double* sell_price, double* buy_price, double* sell_qty, double* buy_qty);

  void CalculateBuyPricePushPull(double buy_price, double* buy_price_push, double* buy_price_pull);
  void
  CalculateSellPricePushPull(double sell_price, double* sell_price_push, double* sell_price_pull);

  void ThriftPrices(double* buy_price, double* sell_price);
  bool SubmitPassiveOrders(
      int64_t timestamp,
      double sell_price,
      double buy_price,
      double sell_qty,
      double buy_qty,
      bool cancel_all_existing_sell,
      bool cancel_all_existing_buy);

  int CheckInput(double price, double qty);
  void ResetAction() {
    cancel_executor_->Clear();
    fire_sell_ = -1;
    fire_buy_ = -1;
  }

  void
  SubmitChinaOrder(int64_t timestamp, OrderSide order_side, double order_price, double order_qty);

  OrderSide GetChinaOrderSide(OrderSide order_side, double order_qty);

 protected:
  void SubmitOrder(int64_t timestamp, OrderSide order_side, double order_price, double order_qty);
  PassiveExecutorConfig config_;
  IOrderSubsystem* os_;
  std::unique_ptr<WorkingOrderState> wo_;

 private:
  OrderQuerier* oq_;
  MarketExchangeApi mea_;
  const TopBook* book_;

  int fire_sell_;
  int fire_buy_;
  double best_ask_;
  double best_bid_;
  int64_t last_buy_ts_;
  int64_t last_sell_ts_;

  double fill_pos_{0};
  int64_t last_fill_ts_{0};
  double og_pos_{0};
  int64_t og_pos_ts_{0};
  bool pos_inited_{false};

  double fill_cooldown_qty_;
  size_t fill_cooldown_cnt_;
  bool use_fill_pos_;
  std::unique_ptr<coin2::strategy::util::MovingSum<double>> buy_fills_;
  std::unique_ptr<coin2::strategy::util::MovingSum<double>> sell_fills_;
  std::unique_ptr<coin2::strategy::util::MovingUnique<int64_t>> buy_fill_cnt_;
  std::unique_ptr<coin2::strategy::util::MovingUnique<int64_t>> sell_fill_cnt_;

  // market share control
  double market_share_limit_;
  std::unique_ptr<coin2::strategy::util::MovingSum<double>> our_buys_;
  std::unique_ptr<coin2::strategy::util::MovingSum<double>> our_sells_;
  std::unique_ptr<coin2::strategy::util::MovingSum<double>> market_buys_;
  std::unique_ptr<coin2::strategy::util::MovingSum<double>> market_sells_;

  std::unique_ptr<CancelExecutor> cancel_executor_;
  std::unique_ptr<IProduct> product_;
  std::unique_ptr<IProductHolder> product_holder_;
  const TickPrice* tick_price_;
  const TickQty* tick_qty_;
  std::string tag_;
  bool enable_order_submission_{true};
};

}  // namespace coin2::strategy::order_executor
